﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Binalyse
{
    class HexViewer : Control
    {
        private void InitializeComponent()
        {
            this.SuspendLayout();

            this.DoubleBuffered = true;
            this.Name = "HexViewer";
            this.Size = new System.Drawing.Size(700, 700);
            this.FontChanged += new EventHandler(HexViewer_FontChanged);
            this.Paint += new System.Windows.Forms.PaintEventHandler(HexViewer_Paint);
            this.Resize += new System.EventHandler(HexViewer_Resize);
            this.MouseClick += new MouseEventHandler(HexViewer_MouseClick);
            this.MouseWheel += new MouseEventHandler(HexViewer_MouseWheel);

            this.ResumeLayout(false);
        }

        private const float _nSpaceOnChars = 1.0f;         //字符串间距系数
        private const float _nSpaceOnLines = 0.5f;         //行距系数
        private const float _nSpaceOfMargin = 9.0f;        //左边标签宽度系数

        private byte[] btsBuffer = null;                   //数据
        private int nBufferSize = 0;                       //数据大小
        private float nCellWidth = 0.0f;                   //字符框宽度
        private float nCellHeight = 0.0f;                  //字符框高度
        private float nCharWidth = 0.0f;                   //字符宽度
        private float nCharHeight = 0.0f;                  //字符高度
        private float nLeftLabelWidth = 80.0f;             //左边标签宽度
        private int nSelectIndex = -1;                     //当前所选项
        private int nFirstVisibleLine = 0;                 //当前显示第一行的索引
        private int nCurViewLines = 0;                     //当前显示的行数
        private int nCanViewLines = 0;                     //当前能够显示的行数
        private int nTotalLines = 0;                       //当前需要显示的总行数
        private int nHighLightStart = -1;                  //需特殊强调字节的起始索引
        private int nHighLightLength = 0;                  //需特殊强调字节的数量

        public delegate void SelectedIndexChangedEventHandler(object sender);
        /// <summary>
        /// 当属性SelectedIndex发生更改时，触发此事件
        /// </summary>
        public event SelectedIndexChangedEventHandler SelectedIndexChangedEvent;
        protected virtual void RaiseSelectedIndexChangedEvent()
        {
            if (SelectedIndexChangedEvent != null)  SelectedIndexChangedEvent(this);
        }

        private string[] strHex = 
        {
            "00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
            "10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
            "20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
            "30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
            "40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
            "50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
            "60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
            "70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
            "80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
            "90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
            "A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
            "B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
            "C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
            "D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
            "E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
            "F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF",
        };
        /// <summary>
        /// 要在HEXVIEWER中显示的数据
        /// </summary>
        public byte[] Data
        {
            get { return btsBuffer; }
            set
            {
                btsBuffer = value;
                nBufferSize = value == null ? 0 : btsBuffer.Count();
                nTotalLines = (nBufferSize % 16) == 0 ? nBufferSize / 16 : nBufferSize / 16 + 1;
                nFirstVisibleLine = 0;
                nCurViewLines = nTotalLines <= nCanViewLines ? nTotalLines : nCanViewLines;
                nSelectIndex = nBufferSize > 0 ? 0 : -1;
                nHighLightStart = -1;
                nHighLightLength = 0;
                Invalidate();
            }
        }
        /// <summary>
        /// 当前选择项的索引
        /// </summary>
        public int SelectedIndex
        {
            get { return nSelectIndex; }
            set
            {
                if (nSelectIndex != value)
                {

                    if (value >= 0 && value < nBufferSize)
                    {
                        nSelectIndex = value;
                        RaiseSelectedIndexChangedEvent();
                        Invalidate();
                    }
                }
            }
        }
        /// <summary>
        /// 设置强调显示的字节起始索引及数量
        /// </summary>
        /// <param name="nStart">起始索引</param>
        /// <param name="nLength">数量</param>
        public void SetHighLight(int nStart, int nLength)
        {
            nHighLightStart = nStart;
            nHighLightLength = nLength;
            Invalidate();
        }
        public int HighLightStart { get { return nHighLightStart; } }
        public int HighLightLength { get { return nHighLightLength; } }

        /// <summary>
        /// 当前视图滚动至指定行
        /// </summary>
        /// <param name="line">指定行的行号，以0起始</param>
        /// <returns>是否成功</returns>
        public bool ScroolToLine(int line)
        {
            if (line < 0 || line >= nTotalLines) return false;
            //已显示所有行，无需滚动，直接返回
            if (nTotalLines <= nCanViewLines) return true;
            //要滚动至的行在最后一页中
            if (line + nCanViewLines >= nTotalLines) 
            {
                nFirstVisibleLine = nTotalLines - nCanViewLines;
            }
            else
            {
                nFirstVisibleLine = line;
            }
            nCurViewLines = nCanViewLines;
            Invalidate();
            return true;
        }
        /// <summary>
        /// 滚动至SelectedIndex所在行
        /// </summary>
        /// <returns>是否成功</returns>
        public bool ScrollToCaret()
        {
            return ScroolToLine(nSelectIndex / 16);
        }
        private void MeasureText()
        {
            Graphics g = CreateGraphics();

            SizeF size = g.MeasureString("0123456789ABCDEF", Font);
            nCharWidth = size.Width / 16.0f;
            nCharHeight = size.Height;
            nCellWidth = (2.0f + _nSpaceOnChars) * nCharWidth;
            nCellHeight = (1.0f + _nSpaceOnLines) * nCharHeight;
            nLeftLabelWidth = 9 * nCharWidth;

            g.Dispose();
        }
        /// <summary>
        /// SelectedIndex所在行是否可见
        /// </summary>
        /// <returns>true为可见</returns>
        public bool IsSelectedCellVisible()
        {
            return (nSelectIndex >= nFirstVisibleLine * 16) && (nSelectIndex < (nFirstVisibleLine + nCurViewLines) * 16);
        }

        public HexViewer()
        {
            InitializeComponent();
            MeasureText();
        }

        void HexViewer_FontChanged(object sender, EventArgs e)
        {
            MeasureText();
        }

        private void HexViewer_Paint(object sender, PaintEventArgs e)
        {
            float x, y, xChar;
            Graphics g = e.Graphics;
            Font font = this.Font;
            Brush foreGround = new SolidBrush(this.ForeColor);
            Pen penHighLight = new Pen(this.ForeColor, 1.0f);

            //Header
            x = nLeftLabelWidth + 0.5f * (nCellWidth - 2.0f * nCharWidth);
            y = 0.5f * (nCellHeight - nCharHeight);
            for (int i = 0; i < 16; i++)
            {
                g.DrawString(string.Format("+{0:X1}", i), font, foreGround, x, y);
                x += nCellWidth;
            }
            //Label
            int nStartAddress = nFirstVisibleLine;
            int nPosStart = nStartAddress * 16;
            int nPosEnd = nPosStart + nCurViewLines * 16 - 1;
            if (nPosEnd >= nBufferSize) nPosEnd = nBufferSize - 1;
            int nEndAddress = (nPosEnd - nPosStart) % 16 == 0 ?
                nStartAddress + (nPosEnd - nPosStart) / 16 : nStartAddress + (nPosEnd - nPosStart) / 16 + 1;

            x = 0.5f * (nCellWidth - 2.0f * nCharWidth);
            y = 0.5f * (nCellHeight - nCharHeight) + nCellHeight;

            for (int i = nFirstVisibleLine; i < nFirstVisibleLine + nCurViewLines; i++)
            {
                g.DrawString(string.Format("{0:X8}", i * 16), font, foreGround, x, y);
                y += nCellHeight;
            }

            //HEX
            x = nLeftLabelWidth + 0.5f * (nCellWidth - 2.0f * nCharWidth);
            y = 0.5f * (nCellHeight - nCharHeight) + nCellHeight;
            xChar = x + 16 * nCellWidth;
            int nCount = 1;
            for (int i = nPosStart; i < nPosEnd + 1; i++)
            {
                //是选择项
                if (i == nSelectIndex)
                {
                    g.FillRectangle(Brushes.YellowGreen, x, y, nCharWidth * 2.0f, nCharHeight);
                    g.FillRectangle(Brushes.YellowGreen, xChar, y, nCharWidth, nCharHeight);
                }
                //绘制二进制字符
                g.DrawString(strHex[btsBuffer[i]], font, foreGround, x, y);
                //是强调项
                if (i >= nHighLightStart && i < nHighLightStart + nHighLightLength)
                {
                    g.DrawLine(penHighLight, x - 0.5f * (nCellWidth - 2.0f * nCharWidth),
                        y + 1.0f + nCharHeight, x + 0.5f * nCellWidth + nCharWidth, y + 1.0f + nCharHeight);
                }
                //是可打印的字符
                if (btsBuffer[i] > 32 && btsBuffer[i] < 127)
                {
                    g.DrawString(((char)btsBuffer[i]).ToString(), font, foreGround, xChar, y);
                }
                else
                {
                    g.DrawString(".", font, Brushes.DarkBlue, xChar, y);
                }
                
                x += nCellWidth;
                xChar += nCharWidth;
                if (nCount % 16 == 0)
                {
                    x = 0.5f * (nCellWidth - 2.0f * nCharWidth) + nLeftLabelWidth;
                    xChar = x + 16 * nCellWidth;
                    y += nCellHeight;
                }
                nCount++;
            }

        }

        private void HexViewer_Resize(object sender, EventArgs e)
        {
            nCanViewLines = (int)(this.Height / nCellHeight - 1);
            //设置显示的行数信息
            if (nCanViewLines < nTotalLines) //当前视图需要滚动才能完全显示
            {
                if (nFirstVisibleLine + nCanViewLines > nTotalLines)//已滚动至最后一页
                {
                   nCurViewLines = nTotalLines - nFirstVisibleLine;
                }
                else//未滚动至最后一页
                {
                    nCurViewLines = nCanViewLines;
                }
            }
            else//无需滚动
            {
                nFirstVisibleLine = 0;
                nCurViewLines = nTotalLines;
            }
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            switch (keyData)
            {
                case Keys.Down:
                    this.SelectedIndex += 16;
                    if (IsSelectedCellVisible() == false) ScrollToCaret();
                    return true;
                case Keys.Up:
                    this.SelectedIndex -= 16;
                    if (IsSelectedCellVisible() == false) ScrollToCaret();
                    return true;
                case Keys.Left:
                    this.SelectedIndex--;
                    if (IsSelectedCellVisible() == false) ScrollToCaret();
                    return true;
                case Keys.Right:
                    this.SelectedIndex++;
                    if (IsSelectedCellVisible() == false) ScrollToCaret();
                    return true;
                case Keys.PageDown:
                    this.ScroolToLine(Math.Min(nFirstVisibleLine + nCanViewLines, nTotalLines - 1));
                    return true;
                case Keys.PageUp:
                    this.ScroolToLine(Math.Max(0, nFirstVisibleLine - nCanViewLines));
                    return true;
                default:
                    return base.ProcessDialogKey(keyData);
            }
        }
        void HexViewer_MouseClick(object sender, MouseEventArgs e)
        {
            if (this.Focused == false)
            {
                this.Focus();
            }
            else
            {
                if (e.X < nLeftLabelWidth) return;
                if (e.Y < nCellHeight) return;
                int nClickLine = (int)((e.Y - nCellHeight) / nCellHeight) + nFirstVisibleLine;
                int nClickColumn = (int)((e.X - nLeftLabelWidth) / nCellWidth);
                SelectedIndex = nClickLine * 16 + nClickColumn;
            }
        }
        void HexViewer_MouseWheel(object sender, MouseEventArgs e)
        {
            int numberOfTextLinesToMove = -e.Delta * SystemInformation.MouseWheelScrollLines / 120;
            //this.ScrollValue += numberOfTextLinesToMove;
            this.ScroolToLine(nFirstVisibleLine + numberOfTextLinesToMove);
            Invalidate();
        }
    }
}
